package com.api.user.service.impl;

import com.api.user.dao.CoinTransactionMapper;
import com.api.user.dao.UserInfoMapper;
import com.api.user.exception.CoinChangedException;
import com.api.user.exception.CoinNotEnoughException;
import com.api.user.model.CoinTransaction;
import com.api.user.model.UserInfo;
import com.api.user.service.CoinTransactionService;
import com.api.core.service.AbstractService;
import com.api.user.vo.CoinTransactionVo;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

import com.api.common.JSONUtils;
import com.api.core.response.Result;
import com.api.core.response.ResultGenerator;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.util.CollectionUtils;

/**
 * Created by wanghuiwen on 2021/03/26.
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class CoinTransactionServiceImpl extends AbstractService<CoinTransaction> implements CoinTransactionService {
    @Resource
    private CoinTransactionMapper coinTransactionMapper;
    @Resource
    private UserInfoMapper userInfoMapper;

    @Override
    public Result list(String search, String order, Integer page, Integer size){
        Map<String, Object> params = JSONUtils.json2map(search);
        Map<String, Object> orderParams = JSONUtils.json2map(order);
        for (String key : orderParams.keySet()) {
                if (orderParams.get(key) != null && orderParams.get(key).equals("ascending")) orderParams.put(key, "asc");
                if (orderParams.get(key) != null && orderParams.get(key).equals("descending")) orderParams.put(key, "desc");
            }
        PageHelper.startPage(page, size);
        List<Map<String, Object>> res = coinTransactionMapper.baseList(params, orderParams);
        PageInfo<Map<String, Object>> pageInfo = new PageInfo<>(res);
        return ResultGenerator.genSuccessResult(pageInfo);
    }

    @Override
    public Result addOrSub(CoinTransaction coinTransaction) {

        if(coinTransaction.getFromUserId()!=null){
            UserInfo userInfo = userInfoMapper.selectByPrimaryKey(coinTransaction.getFromUserId());

            coinTransaction.setCoinBefore(userInfo.getCoin());
            userInfo.setCoin(userInfo.getCoin().add(coinTransaction.getAmountBalance()));
            coinTransaction.setToCoinAfter(userInfo.getCoin());
            userInfoMapper.updateByPrimaryKeySelective(userInfo);
        }


        if(coinTransaction.getToUserId()!=null){
            UserInfo toUser = userInfoMapper.selectByPrimaryKey(coinTransaction.getToUserId());
            coinTransaction.setToCoinBefore(toUser.getCoin());
            toUser.setCoin(toUser.getCoin().add(coinTransaction.getAmountBalance()));
            coinTransaction.setToCoinAfter(toUser.getCoin());
            userInfoMapper.updateByPrimaryKeySelective(toUser);
        }

        save(coinTransaction);
        return ResultGenerator.genSuccessResult();
    }

    @Override
    public Result listUser(Map<String, Object> params, String order, Integer page, Integer size) {
        Map<String, Object> orderParams = JSONUtils.json2map(order);
        for (String key : orderParams.keySet()) {
            if (orderParams.get(key) != null && orderParams.get(key).equals("ascending")) orderParams.put(key, "asc");
            if (orderParams.get(key) != null && orderParams.get(key).equals("descending")) orderParams.put(key, "desc");
        }
        PageHelper.startPage(page, size);
        List<CoinTransactionVo> vos = coinTransactionMapper.listUser(params,orderParams);
        PageInfo<CoinTransactionVo> pageInfo = new PageInfo<>(vos);
        return ResultGenerator.genSuccessResult(pageInfo);
    }

    @Override
    public void batchTransaction(List<CoinTransaction> coinTransactions) throws CoinNotEnoughException, CoinChangedException {
        Set<Long> allUserIds = new HashSet<>();
        final Map<Long, BigDecimal> subCoinMap = Maps.newHashMapWithExpectedSize(coinTransactions.size());
        for (CoinTransaction coinTransaction : coinTransactions) {
            if (coinTransaction.getFromUserId() != null) {
                Long userId = coinTransaction.getFromUserId();
                allUserIds.add(userId);
                BigDecimal totalSubCoins = subCoinMap.get(userId);
                if (totalSubCoins == null) {
                    totalSubCoins = BigDecimal.ZERO;
                }
                subCoinMap.put(coinTransaction.getFromUserId(), totalSubCoins.add(coinTransaction.getAmountBalance()));
            }
            if (coinTransaction.getToUserId() != null) {
                allUserIds.add(coinTransaction.getToUserId());
            }
        }
        List<UserInfo> userInfos = userInfoMapper.selectByIds(Joiner.on(",").join(allUserIds));
        Map<Long, BigDecimal> userCoinsMap = Maps.newHashMapWithExpectedSize(userInfos.size());
        for (UserInfo userInfo : userInfos) {
            userCoinsMap.put(userInfo.getUserId(), userInfo.getCoin());
        }
        ImmutableList<Long> coinNotEnoughUserIds = FluentIterable.from(userInfos)
                .filter(new Predicate<UserInfo>() {
                    @Override
                    public boolean apply(UserInfo input) {
                        return subCoinMap.containsKey(input.getUserId()) && input.getCoin().compareTo(subCoinMap.get(input.getUserId())) < 0;
                    }
                })
                .transform(new Function<UserInfo, Long>() {
                    @Override
                    public Long apply(UserInfo input) {
                        return input.getUserId();
                    }
                })
                .toList();
        if (!CollectionUtils.isEmpty(coinNotEnoughUserIds)) {
            throw new CoinNotEnoughException(new ArrayList<>(coinNotEnoughUserIds));
        }
        for (CoinTransaction coinTransaction : coinTransactions) {
            if (coinTransaction.getFromUserId() != null) {
                Long userId = coinTransaction.getFromUserId();
                BigDecimal beforeCoin = userCoinsMap.get(userId);
                BigDecimal afterCoin = beforeCoin.subtract(coinTransaction.getAmountBalance());
                int i = userInfoMapper.updateCoin(userId, afterCoin, beforeCoin);
                if (i != 1) {
                    throw new CoinChangedException();
                }
                coinTransaction.setCoinBefore(beforeCoin);
                coinTransaction.setCoinAfter(afterCoin);
                userCoinsMap.put(userId, afterCoin);
            }
            if (coinTransaction.getToUserId() != null) {
                Long userId = coinTransaction.getToUserId();
                BigDecimal beforeCoin = userCoinsMap.get(userId);
                BigDecimal afterCoin = beforeCoin.add(coinTransaction.getAmountBalance());
                int i = userInfoMapper.updateCoin(userId, afterCoin, beforeCoin);
                if (i != 1) {
                    throw new CoinChangedException();
                }
                coinTransaction.setToCoinBefore(beforeCoin);
                coinTransaction.setToCoinAfter(afterCoin);
                userCoinsMap.put(userId, afterCoin);
            }
        }
        coinTransactionMapper.insertList(coinTransactions);
    }
}
